import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;


public class Client extends Thread {
	// Set nodbmode to true, allow client to run without server
	boolean nodbmode = false;
	
	Socket server;
	URL url;
	
	Monitor monitor;
	Object monitorWaitObject = new Object();
	public User user;
	
	Vector<Twitt> tweets = new Vector<Twitt>();
	String following;
	String followers;
	
	// Client will start this 3 threads
	TweetsFetcher tweetsFetcher;
	FollowersFetcher followersFetcher;
	FollowingFetcher followingFetcher;
	
	// Variablen to display on monitor
	int totalUsersFetched = 0;
	int totalTweetsFetched = 0;
	long runningTime = 0;
	long startTime = 0;
	static long traffic = 0;
	
	// Default nr of page, that can be run parallel
	int maxPageFetcherThreads = 1;
	
	// Client is running or pausing
	boolean running = false;
	
	public Client(Monitor monitor) {
		this.monitor = monitor;
		
		// Start a Thread, that will fetch the tweets
		tweetsFetcher = new TweetsFetcher(this);
		tweetsFetcher.start();
		
		// Start a Thread, that will fetch the followers
		followersFetcher = new FollowersFetcher(this);

		// Start a Thread, that will fetch the followings
		followingFetcher = new FollowingFetcher(this);
	}
	
	private void connect() {
		String host = "localhost";
		int port = 8080;
		try {
			server = new Socket(host, port);
		} catch (UnknownHostException e) {
			System.err.println("Can not connect to Server");
		} catch (IOException e) {
			System.err.println("IOException on Client.java > connect()");
		}
	}
	
	public void disconnect() {
		try {
			server.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	static synchronized public void setTraffic(long length) {
		traffic += length;
	}
	
	private void tellWorkersThatThereAreJobsToDo() {	
		System.out.println("> Start fetching user with id "+ user.id);
		// Fetch user information
		UserInformationFetcher userDataFetcher = new UserInformationFetcher(this);
		userDataFetcher.fetch();
		
		// Fetch following list
		followingFetcher.fetch();
		
		// Fetch followers list
		followersFetcher.fetch();
		
		// Fetch tweets
		synchronized(tweetsFetcher) {
			tweetsFetcher.notify();
		}
	}
	
	String[] offlineUsersList = "232285664,5706532,49573859,17596608,8525682,9943672,89005816,56193484,284698200,158907678,286311652,5795372,17568791,25612932,11739102,28170766,14681605,72457270,20495814,60308942,6535212,15293504,14480299,210033010,243315071,261428872,260009730,113425681,13293162,32613163,19148030,20639592,112627753,59293298,17595439,260893029,16457010,27429139,47418655,15377125,158598455,150185852,227716887,59003,8407952,22585744,61133,36082429,55918282,90068794,10822612,78187268,259581901,27978710,197582975,191229716,216299334,22330739,22017602,122258041,56361481,4488,233364902,50751556,59624844,17674486,21625310,29546945,25203361,37601149,15648675,69140561,8516062,26551204,243287822,25889980,5439702,110823581,14635845,240840343,15717794,22096068,20150502,20798128,25027806,19079396,51518017,15888142,24266406,159124891,949521,115734106,17430220,102323621,7517052,46437774,19479427,30364057,115485051,16691049,34747631,114971521,203008363,32137602,13459742,148976601,29481563,28205442,22990962,27141086,64455095,33644240,22843337,19966557,13521702,1909781,14328066,14651539,24671422,107544554,74712538,16589206,17104751,94636858,19413983,78880692,16343608,6480682,11047142,41644966,179339999,19064312,822571,59865670,5252591,93547212,76786462,111406190,191132389,19103160,20747011,33200015,124631720,18992997,17027590,10961962,130251111,67492926,108498344,38626567,5793622,57371662,58550415,22424855,432093,50011708,81783051,179384956,73520854,14433924,209866026,18674255,106156986,88806485,6160792,52700784,8834882,16329199,23944104,6844292,795244,18492638,23631859,13743532,16018805,47652631,10917372,193367073,77142898,51574477,112521116,32887012,19238073,14855279,12048342,22826666,190048720,21711511,14097434,14677919,8161232,10670382,2087371,291,13,763564,83017467,11252962,75069067,14824849,39546218,31503442,130074775,17424053,8257362,18073229,30077969,17864230,93017945,14202711,50735431,180505807,15588657,26197030,30331087,18128265,28165335,92367751,15510569,14253109,23857435,14097951,128784278,4558,46751590,15328330,9715522,156984472,60970661,26421067,15693261,20609518,143602582,72403,15211564,80638910,175210539,166369152,169686021,14305022,23103129,14778255,20263710,114870386,44428795,29222150,13623532,19941836,59715312,781293,23083404,160958767,15469101,18337283,22937232,14138167,22832208,37836732,153810519,76079281,154402274,17977475,124777527,127355628,65992743,22255654,25517196,14325136,677833,150723226,87909252,17543791,15808761,14434070,19697415,15846407,16190898,30023434,17448965,4898091,129920896,15374359,71165241,143624781,5768872,5385912,8110562,14221193,6324102,9499882,26133822,8632782,142314474,17113664,14222652,19924969,15140914,22598729,6017542,2735631,14899922,23796968,35021299,52544275,17454341,58056537,17877122,14217749,137507104,129727090,8372832,132561098,43024351,22953665,28175092,17560096,9448322,24617301,34192959,2049071,52432657,12798452,18350619,17163514,64411570,2404341,16461246,129676929,16939148,28506038,46591322,1318181,3034251,126114892,17474988,15591045,16224767,652983,52847728,5968272,16985149,121836937,23458887,103734037,43924553,43230844,95525828,18090180,89978599,43654274,29394365,18596859,78424569,119168161,14250105,81314313,16441638,14237310,14134204,73019561,83971810,14335262,30090568,15120464,689123,118430409,14331688,114819711,115463541,19554706,50670070,115882166,74217470,15072424,17956614,36072985,109702885,17711240,12091452,19058681,111192127,111996593,101020033,89479300,1159251,102545823,20357998,110509537,108382988,13683482,14696557,17294158,1979921,733383,5965332,50485184,25033119,12993892,44771048,17503180,107595104,956501,14761795,21818737,7434272,11893122,107851498,6141832,822824,50393960,26371960,42437497,19658091,14909968,55525953,17243165,15414807,5168531,18206161,94143715,20265254,5988062,5763262,13348,16982481,807095,100906095,96374925,1586501,26686693,19923144,67053176,15150351,39364684,57172253,87454357,107,768175,44802859,23089953,91187004,5517,18534617,42712551,63725986,691353,14195535,17744075,47109945,35432131,5932202,31718338,64884344,20702279,95561736,17298881,31353077,95063428,14181054,14681377,11009852,28117283,989,652193,31209299,15080807,93302391,956841,18664838,9624742,15307727,7144882,14652452,15746084,11622712,18863894,9274492,8071902,18704291,17111803,16910946,22516489,6022912,15117375,12687952,17243913,20118774,14389031,1143031,22699508,70480584,68175769,82947057,16046824,15139707,82497985,69409599,30013509,6385432,37983370,15279429,88582229,9672342,16186995,14939981,15485441,32913737,11814822,71256932,19432203,26784273,15922433,10221,15980575,50200745,40927173,27710301,39860666,84351228,16739704,80145924,67788043,59680612,79565036,54989273,54028259,17269410,19148069,19909946,25215221,30066839,22512883,14087732,46078438,19803365,70805914,52528982,82393947,82184217,82282572,23441355,4607,37738073,15896188,43390671,15034067,11178592,6444472,21469694,54730258,18044341,18781641,12193642,62605366,69907775,17220934,21131816,21044126,17124560,51867759,34047105,18479513,26525924,62591681,15276401,73914423,73918831,14600116,18363508,40892919,41259253,72349499,15125592,69079814,20724654,16635405,62701599,1190051,74507100,54682125,74614066,16536215,48082865,42158080,73531107,35715545,14719656,58328960,16258745,18465629,1821,17112843,8954472,3957021,820661,44113339,70745737,24426175,15921173,20452840,66373311,20659839,10135072,9163182,14906267,662423,5569032,62581962,15908634,2150981,21425690,31941957,30911516,19357297,1378,41924736,9676172,20661924,13737112,24213780,24403061,307,15808765,57118028,26912754,51463,15809733,40259380,63049848,745273,8372782,51573,14320551,26016185,58610772,21795016,11113,23002858,1178011,61138799,58462957,9413702,2970511,30567588,45277386,46911507,24251218,14120151,20692494,43153272,1374581,749963,58326841,31527059,9175762,33847961,46220856,22685200,18511475,13632,15111666,56569558,14130784,16537281,1401881,14345157,35860484,29652955,19853,14191945,10163,17143213,20947956,14708272,45963562,15148205,44687868,1260941,42623349,37683414,47460859,7054502,14715424,14005022,26550063,24045503,26159970,49793,16499647,23502302,36412963,11969,48031934,20558567,774909,29766089,16835274,40247252,19784831,11388382,3191321,1956841,16332522,45880233,875511,15433693,21808310,45206852,25773340,16877855,15066958,44480157,24047612,13565472,13018112,42638105,14258521,666073,40959360,6238012,18489946,35259477,990,1405221,22097405,11035732,28641052,31072722,17498493,26642006,16692761,22539688,18667371,14346209,17004618,14361155,37570179,598083,17392445,7868832,15199539,16160352,17798493,16843180,18228898,20992556,10041802,15792298,5215,16534784,16935904,8072642,8091632,17174309,28715099,23859156,6331462,16589879,6894202,30738592,14574898,14133162,23643414,7440462,10078,17908607,14097899,34610651,16524285,18202601,34364681,33039483,18789071,17348735,10710802,187,46063,31523219,16103388,662323,17074440,20109973,5660912,21215805,19397785,16156551,16581604,27988939,16003520,7235632,24212474,24943780,31019105,16034755,648,15491176,5668942,29617703,5251831,28120609,14230524,947721,42983,23789166,28206583,12273062,10518692,14663912,14646952,17076483,9156722,14976011,9703302,15703349,3144281,16718443,14351837,14406300,896141,14054926,4603771,15137329,1095131,14474873,16392267,11347122,76013,14541402,5819322,23056433,20749410,13330522,19394188,20713061,16957618,17266844,18691131,21907406,6708952,657863,3840,13496142,1004,16710435,12101862,862681,20623970,972651,16133048,15227650,12956682,712733,5283382,16818600,17461978,19086787,16616097,16553353,1562231,8807152,2384071,19364978,1000591,14839646,7808262,7681662,14538938,14058580,1539471,15258540,2363571,15903746,939091,16047002,15481606,8288752,5845242,16425887,15641893,5380672,696513,16100473,16319797,7939482,2425151,9488342,8654602,760892,15129658,820394,15745848,749863,14458660,15767907,790249,10032112,14079576,414,15249156,15441844,257,786741,4737,1468401,15076390,10583402,14387295,12972652,15131310,4503491,14523801,14246171,14392347,14895312,3690,899701,1745,11304022,446,12052632,12793852,7209912,14946367,4067601,14228118,10759032,14693823,14220046,346,6273552,2730471,10419,14363273,14337915,14368074,12863272,14245742,7015112,13764102,5987782,1970,10239,7040932,14100237,14165186,8349902,14084271,8298392,14047370,13839322,5380,3533231,3930521,12676252,4794061,731573,2171511,5746352,9213742,10723232,8943682,756394,7785872,8729502,5408142,11516172,9640752,2698711,6102602,740983,9643402,768632,7712452,6325362,8777612,6325852,7782372,6267452,8722,18,3882791,57,28243,3713811,7745262,94,8940882,6015992,336113,6501692,8409982,6429102,5817372,650013,2148071,36823,5248441,11882,7615222,7573872,7583332,783214,6735,10051,322,2518571,6549912,6549822,418,5997662,78733,893211,681,992151,5976432,5671022,813286,5578742,1374001,5465042,422,2857721,769254,926981,295,782575,4809381,11885,13479,11522,1085,799708,3485831,5699,716923,808627,1927581,3475,11041,804204,1293261,5017,528,2172,10350,616113,3922,763512,12615,7265,758554,756582,755415,729193,309073,682813,671343,651843,58963,15,428333,43093,5202,1668,383033,374023,36183,260,18713,81033,79053,63793,5574,1153,44463,46413,1075,888,12291,11957,38,7846,4902,711,7266,344,6106,4674,5844,409,348,4776,4489,5061,2784,292,3221,3114,1583,662,988,150,70,380,277,338,334,299,259,235,126,104,56,87,92,21,76,64,67,62,61,60,59,58,31,24,41,23,17,16,14,67260054".split(",");
	int currentUserIndex = -1;
	public void getUserToUpdate() {
		clearCache();

		if(nodbmode) {
			currentUserIndex++;
			if(currentUserIndex >= offlineUsersList.length) currentUserIndex = 0;
			user = new User(offlineUsersList[currentUserIndex],null,null,null,null,null,null,null);
			tellWorkersThatThereAreJobsToDo();
		}
		else {
			try {
				// We try to get Job from server, until the responsed command successed
				int command = -1;
				while(command != 0) {
					// Get Command-Object from Server
					connect();
					ObjectOutputStream os = new ObjectOutputStream(server.getOutputStream());
					Command com = new Command(null,0);
					os.writeObject(com);
					
					ObjectInputStream is = new ObjectInputStream(server.getInputStream());
					com = (Command) is.readObject();
					
					is.close();
					disconnect();

					command = com.commandId;
					if(command == 0) { // Command 0 means a user is inside
						// We return the user (which should be inside the command-object)
						user = (User)com.data;
						tellWorkersThatThereAreJobsToDo();
					}
					else {
						// If failed, we try after 1s
						System.err.println("Bad command received. Retry after 1 second");
						Thread.sleep(1000);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	// Return followerURL to fetch
	public URL getFollowersURL() {
		try {
			URL url = new URL("http://api.twitter.com/1/followers/ids.xml?user_id="+ user.id);
			return url;
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	// Return followingURL to fetch
	public URL getFollowingURL() {
		URL url = null;
		try {
			url = new URL("http://api.twitter.com/1/friends/ids.xml?user_id="+ user.id);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		return url;
	}

	public void hasDoneFetchingTweets() {
		Twitt[] array = new Twitt[tweets.size()];
		
		for(int i = 0; i < tweets.size(); i++) {
			array[i] = tweets.get(i);
		}
		
		user.twitts = array;
		System.out.println(" + Total tweets ("+ array.length +")");
		
		doneWithOneUser();
	}
	
	synchronized public void handleUserDataResults(HashMap<String, String> data) {
		if(data == null) return;
		
		user.description = data.get("description");
		user.location = data.get("location");
		user.name = data.get("name");
		user.screenName = data.get("screen_name");
		user.url = data.get("url");
	}

	synchronized public void handleTweetResults(Twitt[] tweetsOnPage) {
		totalTweetsFetched += tweetsOnPage.length;
		monitor.totalTweets.setText(totalTweetsFetched+" tweets");
		
		for(int i = 0; i < tweetsOnPage.length; i++) {
			tweets.add(tweetsOnPage[i]);
		}
		
		System.out.println("   + fetched page ("+ tweetsOnPage.length +")");
	}

	synchronized public void handleFollowerResults(String followers) {		
		user.followers = followers;
	}
	
	// followingThread has returned
	synchronized public void handleFollowingResults(String following) {
		user.following = following;
	}
	
	// We clear the cache to able to store next user
	private void clearCache() {
		following = "";
		followers = "";
		tweets.clear();
	}

	synchronized private void doneWithOneUser() {
		// Just for debugging
		/*
		System.err.println("##################");
		System.err.println("Screen Name:"+ user.screenName);
		System.err.println("URL:"+ user.url);
		System.err.println("Description:"+ user.description);
		System.err.println("Location:"+ user.location);
		System.err.println("Name:"+ user.name);
		System.err.println("##################");
		*/
		
		// We send the data back to Server
		if(!nodbmode) packAndSend();

		totalUsersFetched++;
		monitor.totalUsers.setText(totalUsersFetched+" users");
		
		if(isRunning()) {
			// Request the next user from Server
			getUserToUpdate();
		}
	}
	
	public void stopClient(String text) {
		running = false;
		runningTime = System.currentTimeMillis() / 1000 - startTime;
		monitor.stop.setText(text);
	}
	
	public void startClient() {
		running = true;
		
		tweetsFetcher.tryToStartPageFetchers();
		
		// Set time point to update totalrunning time
		startTime = System.currentTimeMillis() / 1000;

		synchronized(monitorWaitObject) {
			monitorWaitObject.notifyAll();			
		}
		
		monitor.stop.setText("Stop");
		
		getUserToUpdate();
	}
	
	synchronized public int getMaxPageFetcherThreads() {
		return maxPageFetcherThreads;
	}
	
	synchronized public void increaseMaxPageFetcherThreads() {
		maxPageFetcherThreads++;
		tweetsFetcher.startNewPageFetcher();
		tweetsFetcher.decreaseStoppingKids();
		if(running) {
			synchronized(tweetsFetcher.hasJob) {
				tweetsFetcher.hasJob.notifyAll();
			}
		}
	}
	
	synchronized public void decreaseMaxPageFetcherThreads() {
		if(maxPageFetcherThreads <= 1) {
			return;
		}
		maxPageFetcherThreads--;
		tweetsFetcher.increaseStoppingKids();
	}
	
	private void packAndSend() {
		connect();
		Command cmd = new Command(user, 1);

		try {
			ObjectOutputStream out = new ObjectOutputStream(server.getOutputStream());
			out.writeObject(cmd);
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		disconnect();
	}
	
	public void toggleState() {
		if(running == true) {
			stopClient("Start");
		}
		else {
			startClient();
		}
	}
	
	@Override
	public void run() {
		if(nodbmode) {
			System.err.println("Warning!!! Client.java is on NO Database Mode");
		}
		
		running = true;
		
		// We update the running time in 1s takt
		Timer timer = new Timer();
		timer.schedule(new RunningTime(this), 0, 1000);
		startTime = System.currentTimeMillis() / 1000;
		monitor.stop.setText("Stop");

		getUserToUpdate();
		
		// We let this Client run forever
		try {
			sleep(Long.MAX_VALUE);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public boolean isRunning() {
		return running;
	}

	public void updateRunningTime() {
		if(running) {
			runningTime = System.currentTimeMillis() / 1000 - startTime;
			// Update running time on monitor
			int h = (int)runningTime / 60 / 60;
			int m = (int)runningTime / 60;
			int s = (int)runningTime - h*60*60 - m*60;
			monitor.runningTime.setText(h +"h "+ m +"m "+ s+"s");
			
			
			// Update speed on monitor
			if(runningTime > 0) {
				String speed = (totalTweetsFetched/(int)runningTime) + " tweets/sec";
				monitor.speed.setText(speed);
			}
			
			// Update total traffic
			long temp = traffic;
			if(traffic < 1024) {
				monitor.totalTraffic.setText(temp+" bytes");
			}
			else {
				temp /= 1024;
				if(temp < 1024) {
					monitor.totalTraffic.setText(temp+" kb");
				}
				else {
					temp /= 1024;
					monitor.totalTraffic.setText(temp+" mb");
				}
			}
		}
	}
	
	class RunningTime extends TimerTask {
		Client client;
		public RunningTime(Client client) {
			this.client = client;
		}
		
		public void run()	{
			client.updateRunningTime();
		}
	}
}
